package excel

import (
	"RocFamilySystem/models"
	"fmt"
	"github.com/xuri/excelize/v2"
	"log"
	"reflect"
	"strconv"
	"strings"
)

// ValuePosition 值所在Excel的位置
type ValuePosition struct {
	Position   string //位置
	FieldName  string //字段名
	HeaderName string //表头名称
}

// ReadExcel 读取excel
// file excel库文件
// sheetName 要操作的具体工作蒲
// position 如果是要转换的是结构体的话，则需要传递结构体变量所在excel中的位置坐标
// startLine 起始行，如果是切片则需要传递
// target 要转换的结构体或者数组必须为指针
func ReadExcel(file *excelize.File, sheetName string, position []ValuePosition, startLine int, target any) {
	//获取sheet坐标
	index, err := file.GetSheetIndex(sheetName)
	if err != nil {
		log.Panicln("读取excel【", sheetName, "】工作簿失败", err.Error())
	}
	//设置目标工作簿激活
	file.SetActiveSheet(index)

	targetType := reflect.TypeOf(target).Elem()
	targetValue := reflect.ValueOf(target).Elem()

	//如果是数组则是要读取列表
	if reflect.Slice == targetType.Kind() {
		//切片元素类型
		sliceEleType := targetValue.Type().Elem()
		if targetValue.IsNil() {
			//如果target切片是空的则创建切片
			targetValue.Set(reflect.MakeSlice(reflect.SliceOf(sliceEleType), 0, 0))
		}
		//默认读取列表从第二行开始
		rows, _ := file.GetRows(sheetName)
		//最后一行
		endLine := len(rows)
		for startLine <= endLine {
			//要添加的元素
			appendEle := reflect.New(sliceEleType)

			//处理映射
			for _, ele := range position {
				value, _ := file.GetCellValue(sheetName, fmt.Sprintf("%s%d", ele.Position, startLine))
				//设置值
				setValue(&appendEle, ele, value)
			}

			//最后加元素添加
			newSlice := reflect.Append(targetValue, appendEle.Elem())
			targetValue.Set(newSlice)
			startLine++
		}
		return
	}

	//是结构体类型
	if reflect.Struct != targetType.Kind() {
		return
	}

	for _, ele := range position {
		value, _ := file.GetCellValue(sheetName, ele.Position)
		setValue(&targetValue, ele, value)
	}
}

// WriterExcel 写入excel
// file excel库文件
// sheetName 要操作的具体工作蒲
// position 如果是要转换的是结构体的话，则需要传递结构体变量所在excel中的位置坐标
// startLine 起始行，如果是切片则需要传递
// target 要转换的结构体或者数组
func WriterExcel(file *excelize.File, sheetName string, position []ValuePosition, startLine int, target any) {
	sheetIndex, _ := file.NewSheet(sheetName)
	file.SetActiveSheet(sheetIndex)

	targetType := reflect.TypeOf(target).Elem()
	targetValue := reflect.ValueOf(target).Elem()

	//是数组
	if targetType.Kind() == reflect.Slice {

		//第一行是表头

		//表头样式
		borderStyles := []excelize.Border{
			{Type: "bottom", Color: "CCCCCC", Style: 1},
		}
		oddStyle, _ := file.NewStyle(&excelize.Style{
			Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center"},
			Fill:      excelize.Fill{Type: "pattern", Color: []string{"#b2bec3"}, Pattern: 1},
			Border:    borderStyles,
		})
		eveStyle, _ := file.NewStyle(&excelize.Style{
			Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center"},
			Fill:      excelize.Fill{Type: "pattern", Color: []string{"#dfe6e9"}, Pattern: 1},
			Border:    borderStyles,
		})

		var style int
		for i := range targetValue.Len() {
			eleValue := targetValue.Index(i)
			//判断当前行是奇数还是偶数，切换不同的颜色
			if i%2 == 0 {
				style = eveStyle
			} else {
				style = oddStyle
			}
			file.SetRowHeight(sheetName, startLine, 20)
			for _, ele := range position {
				cellPosition := fmt.Sprintf("%s%d", ele.Position, startLine)

				var fieldValue any
				field := eleValue.FieldByName(ele.FieldName)

				if field.Kind() == reflect.Pointer {
					if !field.IsNil() {
						fieldValue = field.Elem().Interface()
						//判断是否是自定义日期
						localTime, ok := fieldValue.(models.LocalTime)
						if ok {
							formatTimeStr, _ := localTime.MarshalJSON()
							fieldValue = strings.Trim(string(formatTimeStr), "\"")
						}
					}
				} else {
					fieldValue = field.Interface()
				}
				file.SetCellValue(sheetName, cellPosition, fieldValue)
				//设置值和样式
				file.SetCellStyle(sheetName, cellPosition, cellPosition, style)
			}
			startLine++
		}
		return
	}

	//是结构体
	for _, ele := range position {
		fieldValue := targetValue.FieldByName(ele.FieldName).Elem().Interface()
		file.SetCellValue(sheetName, ele.Position, fieldValue)
	}
}

// GeneratorHeader 写入excel
// file excel库文件
// sheetName 要操作的具体工作蒲
// position 如果是要转换的是结构体的话，则需要传递结构体变量所在excel中的位置坐标
func GeneratorHeader(file *excelize.File, sheetName string, position []ValuePosition) {
	index, _ := file.GetSheetIndex(sheetName)
	if index == -1 {
		index, _ = file.NewSheet(sheetName)
	}
	file.SetActiveSheet(index)
	//表头样式
	headStyle, _ := file.NewStyle(&excelize.Style{
		Font: &excelize.Font{
			Color: "#ffffff",
		},
		Border: []excelize.Border{
			{Type: "left", Color: "888888", Style: 1},
			{Type: "top", Color: "888888", Style: 1},
			{Type: "bottom", Color: "888888", Style: 1},
			{Type: "right", Color: "888888", Style: 1},
		},
		Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center"},
		Fill:      excelize.Fill{Type: "pattern", Color: []string{"#0984e3"}, Pattern: 1},
	})
	for _, ele := range position {
		//设置行高
		file.SetRowHeight(sheetName, 1, 40)
		//设置列宽
		file.SetColWidth(sheetName, ele.Position, ele.Position, 20)
		//设置背景颜色
		cellPosition := fmt.Sprintf("%s%d", ele.Position, 1)
		file.SetCellStyle(sheetName, cellPosition, cellPosition, headStyle)
		file.SetCellValue(sheetName, cellPosition, ele.HeaderName)
	}
}

// 设置值
func setValue(target *reflect.Value, ele ValuePosition, value string) {
	//表示有值
	field := (target.Elem()).FieldByName(ele.FieldName)
	fieldTypeKind := field.Type().Kind()
	//如果是指针类型
	if field.Kind() == reflect.Ptr {
		//获取指针之后的数据类型
		fieldTypeKind = field.Type().Elem().Kind()
	}
	switch fieldTypeKind {
	case reflect.Int8:
	case reflect.Int16:
	case reflect.Int32:
	case reflect.Int64:
	case reflect.Int:
		num, _ := strconv.Atoi(value)
		if field.Kind() == reflect.Ptr {
			field.Set(reflect.ValueOf(&num))
			break
		}
		field.Set(reflect.ValueOf(num))
		break
	case reflect.Float32:
	case reflect.Float64:
		num, _ := strconv.ParseFloat(value, 64)
		if field.Kind() == reflect.Ptr {
			field.Set(reflect.ValueOf(&num))
			break
		}
		field.Set(reflect.ValueOf(num))
		break
	case reflect.String:
		if field.Kind() == reflect.Ptr {
			field.Set(reflect.ValueOf(&value))
			break
		}
		field.Set(reflect.ValueOf(value))
		break
	case reflect.Bool:
		num, _ := strconv.ParseBool(value)
		if field.Kind() == reflect.Ptr {
			field.Set(reflect.ValueOf(&num))
			break
		}
		field.Set(reflect.ValueOf(num))
		break
	}
}
